﻿using NineSeas.Models;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
using Contrib.Voting.Services;
using Orchard.Localization;
using Contrib.Voting.Functions;
using Contrib.Voting.Models;
using NineSeas.Extensions;
using Contrib.Taxonomies.Models;
using Contrib.Taxonomies.Services;
using Orchard.Core.Title.Models;
using Orchard.Autoroute.Models;
using NineSeas.ViewModels;
using NHibernate.Linq;
using NHibernate.Criterion;
using NHibernate.Transform;
using Orchard.ContentManagement.Records;
namespace NineSeas.Services
{
    public class BookService: IBookService
    {
        private readonly IOrchardServices _orchardServices;
        private readonly IVotingService _votingService;
        private readonly IRepository<ResultRecord> _resultRepository;
        private readonly IChapterService _chapterService;
        private readonly ITaxonomyService _taxonomyService;
        private readonly IRepository<BookPartRecord> _bookRepository;
        private readonly ICustomRepository<ChapterPartRecord> _chapterRepository;
        private readonly IRepository<TermContentItem> _termContentItemRepository;

        public BookService(IOrchardServices orchardServices, 
            IVotingService votingService,
            IRepository<ResultRecord> resultRepository,
            IChapterService chapterService,
            ITaxonomyService taxonomyService,
            IRepository<BookPartRecord> bookRepository,
            ICustomRepository<ChapterPartRecord> chapterRepository,
            IRepository<TermContentItem> termContentItemRepository)
        {
            this._orchardServices = orchardServices;
            this._votingService = votingService;
            this._resultRepository = resultRepository;
            this._chapterService = chapterService;
            this._taxonomyService = taxonomyService;
            this._bookRepository = bookRepository;
            this._chapterRepository = chapterRepository;
            this._termContentItemRepository = termContentItemRepository;
        }

        public IList<BookSummaryViewModel> Search(ViewModels.BookSearchViewModel search, PaginationInfo paging)
        {
            return Search(search, paging, VersionOptions.Published);
        }
        public IList<BookSummaryViewModel> Search(ViewModels.BookSearchViewModel search, PaginationInfo paging, VersionOptions options)
        {
            var predicate = PredicateBuilder.True<BookPartRecord>();

            if (search.AuthorId.HasValue)
            {
                predicate = predicate.And(b => b.AuthorId == search.AuthorId.Value);
            }

            predicate = ApplySearchExpression(predicate, search.Expression);

            TermPart category = GetCategory(search.Category, options);

            var ids = category != null ? 
                _bookRepository.Table
                    .Where(predicate)
                    .Join(_termContentItemRepository.Table, o => o.Id, i => i.TermsPartRecord.Id, (o, i) => new {Book = o, Joining = i})
                    .Where(t => t.Joining.TermRecord.Id == category.Id)
                    .Select(t => t.Book.ContentItemRecord.Id)
                    .ToList()
                : _bookRepository.Table
                    .Where(predicate)
                    .Select(b => b.ContentItemRecord.Id)
                    .ToList();

            paging.TotalRecords = _orchardServices.ContentManager.Query<BookPart, BookPartRecord>()
                    .ForVersion(options)
                    .Where(b => ids.Contains(b.ContentItemRecord.Id))
                    .Count();
            
            var query = _orchardServices.ContentManager.Query<BookPart, BookPartRecord>()
                    .ForVersion(options)
                    .Where(b => ids.Contains(b.ContentItemRecord.Id));

            query = ApplyOrderBy(query, search.OrderBy, search.OrderDirection);

            var result = query.Slice(paging.StartIndex, paging.PageSize.Value)
                    .Select(b => new BookSummaryViewModel (b))
                    .ToDictionary(b => b.Id.Value);


            if (search.Options != null)
            {
                if (search.Options.ForEdit)
                {
                    ResultForEdit(result);
                }
            }
            return result.Values.ToList();
        }

        public IList<BookSummaryViewModel> GetBooksByAuthor(int authorId, PaginationInfo paging)
        {
            Expression<Func<BookPartRecord, bool>> predicate = b => b.AuthorId == authorId;
            paging.TotalRecords = GetCount(predicate);

            return GetList(predicate, paging);                  
        }

        public BookPart GetByName(string name)
        {
            return _orchardServices.ContentManager.Query<BookPart, BookPartRecord>()
                .Where(b => b.NameOnUrl == name)
                .Slice(1)
                .FirstOrDefault();
        }

        public BookValidationResult ValidateBook(ViewModels.BookViewModel bookViewModel)
        {
            return _orchardServices.ContentManager.Query<BookPart, BookPartRecord>()
                .ForVersion(VersionOptions.Published)
                .Where(b => b.NameOnUrl == bookViewModel.NameOnUrl && (bookViewModel.Id == null || bookViewModel.Id != b.ContentItemRecord.Id))
                .Count() == 0 ?
                BookValidationResult.Success : BookValidationResult.NameOnUrlExists;
        }
        
        public void Delete(int id)
        {
            var bookPart = _orchardServices.ContentManager.Get<BookPart>(id);
            if (bookPart == null) return;

            // delete all chapters
            var chapters = _chapterService.GetByBookId(id, new PaginationInfo() { 
                PageSize = Int32.MaxValue
            });

            foreach (var chapter in chapters)
            {
                _chapterService.Delete(chapter);
            }

            _orchardServices.ContentManager.Remove(bookPart.ContentItem);

            _orchardServices.ContentManager.Flush();
        }
        //private 
        private int GetCount(Expression<Func<BookPartRecord, bool>> predicate)
        {
            return _orchardServices.ContentManager.Query<BookPart, BookPartRecord>()
                    .Where(predicate).Count();
        }

        private IList<BookSummaryViewModel> GetList(Expression<Func<BookPartRecord, bool>> predicate, PaginationInfo paging)
        {
            return _orchardServices.ContentManager.Query<BookPart, BookPartRecord>()
                    .Where(predicate)
                    .OrderByDescending(b => b.CreatedDate)
                    .Slice(paging.StartIndex, paging.PageSize.Value)
                    .Select(b => new BookSummaryViewModel(b))
                    .ToList();
        }

        private Expression<Func<BookPartRecord, bool>> ApplySearchExpression(Expression<Func<BookPartRecord, bool>> predicate, string input)
        {
            if (!String.IsNullOrEmpty(input) && String.Compare(input.Trim(), "%") != 0)
            {
                string expression = input;
                if (!expression.EndsWith("%"))
                {
                    expression += "%";
                }

                if (!expression.StartsWith("%"))
                {
                    expression = "%" + expression;
                }

                return predicate.And(b => b.Name.Contains(expression));
            }
            else
            {
                return predicate.And(b => b.Name != null);
            }
        }

        private void ResultForEdit(Dictionary<int, BookSummaryViewModel> result)
        {
            var resultIds = result.Values.Select(b => b.Id).ToList();
            var groupResults = _chapterRepository.Table
                .Where(c => resultIds.Contains(c.BookId) && c.ContentItemVersionRecord.Published)
                .GroupBy(c => c.BookId, c => c)
                .Select(g => new
                {
                    BookId = g.Key,
                    ChapterCount = g.Count(),
                    DraftCount = 0,
                    ResultType = 0 // chapters
                })
                .ToList()
                .Union(
                    _chapterRepository.Table
                        .Where(c => resultIds.Contains(c.BookId) && c.ContentItemVersionRecord.Published && !c.ContentItemVersionRecord.Latest)
                        .GroupBy(c => c.BookId, c => c)
                        .Select(g => new
                        {
                            BookId = g.Key,
                            ChapterCount = 0,
                            DraftCount = g.Count(),
                            ResultType = 1 // drafts
                        })
                        .ToList()
                )
            ;


            if (groupResults != null)
            {
                foreach (var item in groupResults)
                {
                    if (!result.ContainsKey(item.BookId)) continue;
                    else if (item.ResultType == 0)
                    {
                        result[item.BookId].ChapterCount = item.ChapterCount;
                    }
                    else if (item.ResultType == 1)
                    {
                        result[item.BookId].DraftCount = item.DraftCount;
                    }
                }
            }
        }

        private TermPart GetCategory(string categoryText, VersionOptions options)
        {
            TermPart category = null;
            if (!String.IsNullOrWhiteSpace(categoryText))
            {
                try
                {
                    var taxonomy = _taxonomyService.GetTaxonomyByName(Constants.Taxonomies.Categories);

                    category = _orchardServices.ContentManager.Query<TermPart, TermPartRecord>()
                        .ForVersion(options)
                        .Where(x => x.TaxonomyId == taxonomy.Id).Join<AutoroutePartRecord>()
                        .Where(x => x.DisplayAlias.Equals(categoryText))
                        .List().FirstOrDefault();
                }
                catch { }
            }

            return category;
        }

        private IContentQuery<BookPart, BookPartRecord> ApplyOrderBy(IContentQuery<BookPart, BookPartRecord> query, string orderBy, string orderDirection)
        {
            Expression<Func<BookPartRecord, object>> orderByClause = null;
            if (!String.IsNullOrWhiteSpace(orderBy))
            {
                if ("likes".Equals(orderBy, StringComparison.InvariantCultureIgnoreCase))
                {
                    orderByClause = b => b.TotalLikes;
                }
                else if ("views".Equals(orderBy, StringComparison.InvariantCultureIgnoreCase))
                {
                    orderByClause = b => b.TotalViews;
                }
                else
                {
                    orderByClause = b => b.Id;
                }

                if (String.IsNullOrWhiteSpace(orderDirection) || !orderDirection.StartsWith("asc", StringComparison.InvariantCultureIgnoreCase))
                {
                    query = query.OrderByDescending(orderByClause);
                }
                else
                {
                    query = query.OrderBy(orderByClause);
                }
            }
            else
            {
                query = query.OrderByDescending(a => a.Id);
            }

            return query;
        }
    }
}